package com.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gmall.gateway.handle.SignValidateHandler;
import com.gmall.gateway.exception.GmallBizException;
import com.gmall.gateway.model.CachedBodyOutputMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.Ordered;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author HL.Wu
 * @date 2020/7/2 17:37
 * Copyright ©https://blog.csdn.net/qq_31150503 Copyright@2009-2020 AII Right Reserve
 */

@Component
@Slf4j
public class SignValidateHandlerFilter implements GlobalFilter, Ordered {

    private final List<HttpMessageReader<?>> messageReaders;

    private final SignValidateHandler signValidateHandler;

    public SignValidateHandlerFilter(SignValidateHandler signValidateHandler) {
        this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
        this.signValidateHandler = signValidateHandler;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
        HttpMethod method = exchange.getRequest().getMethod();
        // mediaType
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)) {
            // TODO 格式非要求 直接返回
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("message","抱歉，不符合格式要求");
            byte[] bytes = JSON.toJSONString(jsonObject).getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
            return exchange.getResponse().writeWith(Flux.just(buffer));
        }
        if (HttpMethod.POST.equals(method) || HttpMethod.GET.equals(method)) {
            // 通用请求方式签名校验.
            return commonMethodHandle(exchange, chain, serverRequest);
        } else {
            // 特殊请求方式签名校验
            return otherMethodTypeHandle(exchange, chain);
        }
    }

    /**
     * Get the order value of this object.
     * <p>Higher values are interpreted as lower priority. As a consequence,
     * the object with the lowest value has the highest priority (somewhat
     * analogous to Servlet {@code load-on-startup} values).
     * <p>Same order values will result in arbitrary sort positions for the
     * affected objects.
     *
     * @return the order value
     * @see #HIGHEST_PRECEDENCE
     * @see #LOWEST_PRECEDENCE
     */
    @Override
    public int getOrder() {
        return 1;
    }

    /**
     * 通用方式请求业务处理
     *
     * @param exchange
     * @param chain
     * @param serverRequest
     * @return
     */
    private Mono<Void> commonMethodHandle(ServerWebExchange exchange, GatewayFilterChain chain, ServerRequest serverRequest) {
        // mediaType
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        Class inClass = String.class;
        // parse body data info
        Mono<?> modifiedBody = serverRequest.bodyToMono(String.class)
                .switchIfEmpty((Mono) signVerifyHandle(serverRequest, "", mediaType))
                // .log("modify_request_mono", Level.INFO)
                .flatMap(body -> signVerifyHandle(serverRequest, body.toString(), mediaType));
        // reset body value
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, inClass);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        //  the new content type will be computed by body Inserted
        // and then set in the request decorator
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                            exchange.getRequest()) {
                        public HttpHeaders getHeaders() {
                            long contentLength = headers.getContentLength();
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            if (contentLength > 0) {
                                httpHeaders.setContentLength(contentLength);
                            } else {
                                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            }
                            return httpHeaders;
                        }

                        public Flux<DataBuffer> getBody() {
                            return outputMessage.getBody();
                        }
                    };
                      // 格式转换
//                    DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
//                    ServerHttpResponseDecorator decoratedResponse = decoratorResponseFormat(exchange, bufferFactory);
//                    chain.filter(exchange.mutate().request(decorator).response(decoratedResponse).build());
                    return chain.filter(exchange.mutate().request(decorator).build());
                }));
    }

    /**
     * 如果中文乱码响应结果接入回执编码格式处理
     *
     * @param exchange
     * @param bufferFactory
     * @return
     */
    private ServerHttpResponseDecorator decoratorResponseFormat(ServerWebExchange exchange, DataBufferFactory bufferFactory) {
        // 设置相应编码格式
        return new ServerHttpResponseDecorator(exchange.getResponse()) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                                return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
                                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                                    DataBuffer join = dataBufferFactory.join(dataBuffer);
                                    byte[] content = new byte[join.readableByteCount()];
                                    join.read(content);
                                    //释放掉内存
                                    DataBufferUtils.release(join);
                                    String respObj = new String(content, StandardCharsets.UTF_8);
                                    log.info("current response info is \n {}", respObj);
                                    byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes(StandardCharsets.UTF_8);
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
    }

    private Mono<Void> otherMethodTypeHandle(ServerWebExchange exchange, GatewayFilterChain chain) {
        //GET 验签
        MultiValueMap<String, String> map = exchange.getRequest().getQueryParams();
        if (!CollectionUtils.isEmpty(map)) {
            JSONObject paramObj = JSON.parseObject(JSON.toJSONString(map));
            // 是否存在Token
            JSONObject msg = signValidateHandler.handle(paramObj);
            if (msg != null) {
                return Mono.error(new GmallBizException(msg.getInteger("code"), msg.getString("msg")));
            }
        }
        return chain.filter(exchange);
    }

    /**
     * token 签名校验
     *
     * @return
     */
    private Mono<? extends String> signVerifyHandle(ServerRequest serverRequest, String body, MediaType mediaType) {
        log.info("current request body info is {}", body);
        JSONObject bodyObj = new JSONObject();
        // 获取请求头参数
        String sign = serverRequest.headers().firstHeader("sign");
        log.info("current sign is : [{}]",sign);
        bodyObj.put("sign", sign);
        String params = "";
        if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) && StringUtils.isNotBlank(body)) {
            // TODO 处理数据封装params
        } else if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) && StringUtils.isNotBlank(body)) {
            // TODO json格式数据
        }
        bodyObj.put("params", params);

        // TODO 签名校验
        JSONObject msg = signValidateHandler.handle(bodyObj);
        if (msg != null) {
            // TODO 签名未校验通过
            return Mono.error(new GmallBizException(msg.getInteger("code"), msg.getString("msg")));
        }
        // new body obj ===> you need to copy a new obj old can not use and reset value
        if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
            return Mono.just(body);
        }
        // body 流形式只能读取一次，需要重新添加流
        JSONObject newObj = new JSONObject(JSON.parseObject(body));
        return Mono.just(encodeBody(newObj));
    }

    private String encodeBody(JSONObject body) {
        return JSON.toJSONString(body);
    }
}
